#include "graphics.h"

void init_graphics(cr_state *state, char *font, int max_w, int max_h, int font_size) {
    state->render_change = 0;

    state->window = SDL_CreateWindow(
        WINDOW_TITLE,
        /* initial pos of window */
        SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
        /* window scale */
        max_w, max_h,
        /* show immediately, highdpi, resizability */
        SDL_WINDOW_SHOWN | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_RESIZABLE
        //SDL_WINDOW_SHOWN | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_RESIZABLE
        /* TODO: fix window resizing */
    );

    if (state->window == NULL) {
        fprintf(stderr, "SDL_CreateWindow error: %s\n", SDL_GetError());
        exit(EXIT_FAILURE);
    }

    /* creates renderer
       -1 specifies the loading of whichever video driver supports our flags
       SDL_RENDERER_ACCELERATED:  use hardware accelerating
       SDL_RENDERER_PRESENTVSYNC: synchronize present function (update display) with monitor refresh rate
    */
    state->renderer = SDL_CreateRenderer(state->window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_TARGETTEXTURE);

    if (state->renderer == NULL) {
        fprintf(stderr, "SDL_CreateRenderer error: %s\n", SDL_GetError());
        exit(EXIT_FAILURE);
    }

    SDL_SetRenderDrawBlendMode(state->renderer, SDL_BLENDMODE_BLEND);

    /* texture for render context */
    state->display = SDL_CreateTexture(state->renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, max_w, max_h);

    /* font to use */
    state->font = NULL;
    if (font != NULL)
        state->font = TTF_OpenFont(font, font_size);
    /* if user tries to open his own font and it doesn't work */
    if (font != NULL && state->font == NULL) {
        fprintf(stderr, "error: user supplied font couldn't load: %s\n", TTF_GetError());
        exit(EXIT_FAILURE);
    }
    /* try different font locations */
    if (state->font == NULL)
        state->font = TTF_OpenFont("/usr/share/fonts/TTF/mplus-1m-regular.ttf", font_size);
    if (state->font == NULL)
        state->font = TTF_OpenFont("./src/font/mplus-1m-regular.ttf", font_size);
    if (state->font == NULL) {
        fprintf(stderr,
                "\nTTF_OpenFont Error:  %s\n",
                SDL_GetError());
        exit(EXIT_FAILURE);
    }
    state->font_height = TTF_FontHeight(state->font);

    SDL_SetRenderTarget(state->renderer, state->display);

    /* group together all the button definitions */
    init_buttons(&state->root, &state->tail);
}

void init_buttons(cr_button_node **root, cr_button_node **tail) {
    cr_drawable empty[2];
    empty[0] = empty_drawable();
    empty[1] = empty_drawable();

    /* toggle status button */
    register_mousedown_event(
        root,
        tail,
        construct_button(
            BUTTON_TOGGLE_STATUS,
            -1, -1, -1, -1,
            &toggle_status,
            empty
        )
    );

    /* next button */
    register_mousedown_event(
        root,
        tail,
        construct_button(
            BUTTON_NEXT,
            -1, -1, -1, -1,
            &next,
            empty
        )
    );

    /* prev button */
    register_mousedown_event(
        root,
        tail,
        construct_button(
            BUTTON_PREV,
            -1, -1, -1, -1,
            &prev,
            empty
        )
    );

    /* aaa_mode button */
    register_mousedown_event(
        root,
        tail,
        construct_button(
            BUTTON_TOGGLE_AAA_MODE,
            -1, -1, -1, -1,
            &toggle_aaa_mode,
            empty
        )
    );
}

void cleanup_graphics(SDL_Window **window, SDL_Renderer **renderer, TTF_Font **font) {
    SDL_DestroyRenderer(*renderer);
    SDL_DestroyWindow(*window);
    TTF_CloseFont(*font);
}

void set_render_change(bool *render_change) {
    *render_change = true;
}

void pre_render(SDL_Renderer **renderer, SDL_Texture **display) {
    SDL_SetRenderTarget(*renderer, *display);
}

void render(SDL_Renderer **renderer, SDL_Texture **display, bool *render_change, cr_drawable *drawables, cr_drawable *drawextras, cr_button_node *root) {
    if (*render_change) {
        /* clear screen */
        SDL_RenderClear(*renderer);

        /* do the important stuff */
        SDL_SetRenderTarget(*renderer, NULL);
        SDL_RenderCopy(*renderer, *display, NULL, NULL);

        /* render all the drawables */
        for (int i = 0; i < TOKEN_ENUM_SIZE; i++) {
            if (drawables[i].texture != NULL) {
                SDL_RenderCopy(*renderer, drawables[i].texture, NULL, &(drawables[i].rect));
                if (drawextras[i].texture != NULL) {
                    SDL_RenderCopy(*renderer, drawextras[i].texture, NULL, &(drawextras[i].rect));
                }
            }
        }

        /* render all the buttons */
        cr_button_node *node = root;
        while (node != NULL) {
            cr_button_node *current = node;
            node = node->next;
            if (current->payload.visual[0].texture != NULL)
                SDL_RenderCopy(*renderer, current->payload.visual[0].texture, NULL, &current->payload.visual[0].rect);
            if (current->payload.visual[1].texture != NULL)
                SDL_RenderCopy(*renderer, current->payload.visual[1].texture, NULL, &current->payload.visual[1].rect);
        }

        SDL_RenderPresent(*renderer);
        *render_change = false;
    }
}

void register_cr_drawable(cr_drawable *drawable, cr_drawable d, bool *render_change) {
    destroy_cr_drawable(drawable);
    drawable->texture = d.texture;
    update_cr_drawable_rect(drawable, &d.rect);
    set_render_change(render_change);
}

void update_cr_drawable_rect(cr_drawable *drawable, SDL_Rect *rect) {
    drawable->rect.x = rect->x;
    drawable->rect.y = rect->y;
    drawable->rect.w = rect->w;
    drawable->rect.h = rect->h;
}

void destroy_cr_drawable(cr_drawable *drawable) {
    if (drawable->texture != NULL)
        SDL_DestroyTexture(drawable->texture);
    drawable->rect.x = 0;
    drawable->rect.y = 0;
    drawable->rect.w = 0;
    drawable->rect.h = 0;
}

cr_drawable empty_drawable() {
    cr_drawable d;
    d.texture = NULL;
    d.rect.x = 0;
    d.rect.y = 0;
    d.rect.w = 0;
    d.rect.h = 0;
    return d;
}

static cr_drawable drawable_text_with_bg(SDL_Renderer **renderer, TTF_Font **font, int x, int y, int max_w, int max_h, char *text, uint32_t color, bool with_bg, bool clip) {
    cr_drawable d = empty_drawable();

    if (text == NULL)
        return d;

    SDL_Color text_rgba = { partr(color), partg(color), partb(color), parta(color) };

    /* ugliest code so far lol */
    /* available_w recalculates border */
    int w, h, available_w = max_w - (max_w + max_h) * 0.034;
    bool duped = false;
    TTF_SizeUTF8(*font, text, &w, &h);
    if (clip && w > available_w) {
        int text_len = strlen(text);
        int avg_width = w / text_len;
        int cut_count = 0;
        duped = true;
        text = strdup(text);

        while (w > available_w) {
            w -= avg_width;
            ++cut_count;
        }

        if (text_len - 4 - cut_count > 0) {
            text[text_len - 4 - cut_count] = '.';
            text[text_len - 3 - cut_count] = '.';
        }
        if (text_len - 2 - cut_count > 0)
            text[text_len - 2 - cut_count] = '\0';
    }

    SDL_Surface* text_surface = TTF_RenderUTF8_Blended(*font, text, text_rgba);

    /* if previously duped, happens during clipping */
    if (duped)
        free(text);
    
    if (text_surface == NULL) {
        fprintf(stderr,
                "\nTTF_RenderText_Solid error:  %s\n",
                SDL_GetError());
        exit(EXIT_FAILURE);
    }

    int message_w = text_surface->w;
    int message_h = text_surface->h;

    SDL_Texture* text_texture = SDL_CreateTextureFromSurface(*renderer, text_surface);
    if (text_texture == NULL) {
        fprintf(stderr,
                "\nSDL_CreateTextureFromSurface Error:  %s\n",
                SDL_GetError());
        exit(EXIT_FAILURE);
    }

    SDL_FreeSurface(text_surface);

    SDL_Rect render_quad = { posx(x, message_w, max_w), posy(y, message_h, max_h), message_w, message_h };
    d.texture = text_texture;
    d.rect = render_quad;
    return d;
}

cr_drawable drawable_text(SDL_Renderer **renderer, TTF_Font **font, int x, int y, int max_w, int max_h, char *text, uint32_t color) {
    return drawable_text_with_bg(renderer, font, x, y, max_w, max_h, text, color, false, true);
}

cr_drawable drawable_cover_art(SDL_Renderer *renderer, unsigned char *image, int image_size, int max_w, int max_h) {
    cr_drawable d;
    d.texture = NULL;

    if (image_size == 0 || image == NULL)
        return d;

    if (DEBUG)
        printf("commence drawing of image\n");

    SDL_Surface *result = NULL;
    SDL_RWops *stream = SDL_RWFromConstMem(image, image_size);
    if (stream == NULL) {
        // error handling for failure - call SDL_GetError()
        fprintf(stderr, "stream NULL: %s\n", SDL_GetError());
        return d;
    } else {
        if (IMG_isPNG(stream))
            result = IMG_LoadPNG_RW(stream);
        else if (IMG_isJPG(stream))
            result = IMG_LoadJPG_RW(stream);
        else {
            /* special case needs pre-emptive exit */
            if (DEBUG)
                fprintf(stderr, "error: cover image neither a png nor jpg!\n");
            if (d.texture != NULL)
                destroy_cr_drawable(&d);
            SDL_RWclose(stream);
            if (image != NULL)
                free(image);
            return d;
        }
        SDL_RWclose(stream);
    }

    if (result == NULL) {
        fprintf(stderr, "surface NULL\n");
        return d;
    }

    SDL_Texture* image_texture = SDL_CreateTextureFromSurface(renderer, result);

    if (image_texture == NULL) {
        fprintf(stderr,
            "\nSDL_CreateTextureFromSurface Error: %s\n",
            SDL_GetError());
        exit(EXIT_FAILURE);
    }

    free(image);
    SDL_FreeSurface(result);

    SDL_Rect render_quad = { 0, 0, max_w, max_h };
    /* center render_quad if need be */
    int w = 0, h = 0, offset_w = 0, offset_h = 0;
    SDL_QueryTexture(image_texture, NULL, NULL, &w, &h);
    scale_sdl_rect_center(&render_quad, w, h, max_w, max_h);

    d.texture = image_texture;
    d.rect = render_quad;
    return d;
}

cr_drawable drawable_bg_rect(SDL_Renderer **renderer, SDL_Rect rect, uint32_t color) {
    /* prepare result */
    cr_drawable d = empty_drawable();

    /* skip invalid boxes */
    if (rect.w <= 0 || rect.h <= 0)
        return d;

    SDL_Surface *bg_surface = SDL_CreateRGBSurface(0, rect.w, rect.h, 32, 0, 0, 0, 0);
    if (bg_surface == NULL) {
        printf("%x %i %i %i %i\n", color, rect.x, rect.y, rect.w, rect.h);
        fprintf(stderr, "error: surface null\n");
        return d;
    }

    SDL_SetSurfaceBlendMode(bg_surface, SDL_BLENDMODE_BLEND);
    SDL_FillRect(bg_surface, NULL, SDL_MapRGB(bg_surface->format, partr(color), partg(color), partb(color)));
    SDL_SetSurfaceAlphaMod(bg_surface, parta(color));

    SDL_Texture* bg_texture = SDL_CreateTextureFromSurface(*renderer, bg_surface);
    if (bg_texture == NULL) {
        fprintf(stderr,
                "\nSDL_CreateTextureFromSurface Error:  %s\n",
                SDL_GetError());
        exit(EXIT_FAILURE);
    }

    SDL_FreeSurface(bg_surface);

    d.texture = bg_texture;
    d.rect = rect;

    return d;
}